package com.naojump.android.gdata.client;

import static org.apache.http.HttpStatus.SC_FORBIDDEN;
import static org.apache.http.HttpStatus.SC_OK;

import android.util.Log;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <strong>Android implementation</strong> for<br>
 * Authentication and Authorization for Google APIs<br>
 * ClientLogin for Installed Applications<br>
 * <br>
 * Example:<br>
 * <code>
 * NClientLogin.Status s = NClientLogin.auth(TAG, "HOSTED_OR_GOOGLE", "e@mail", "pass", "lh2", "Example");<br>
 * if (s instanceof NClientLogin.CaptchaChallenge) {<br>
 * &nbsp;&nbsp;&nbsp;&nbsp;// CAPTHCA Challenge code...<br>
 * } else {<br>
 * &nbsp;&nbsp;&nbsp;&nbsp;// auth failure code...<br>
 * }<br>
 * </code>
 * 
 * @author Naoto Murata
 * @since 1
 * @version 5
 * @see "http://code.google.com/intl/ja/apis/accounts/docs/AuthForInstalledApps.html"
 */
final public class NClientLogin {

    /**
     * <strong>Android implementation</strong> for<br>
     * Authentication and Authorization for Google APIs<br>
     * ClientLogin for Installed Applications<br>
     * <br>
     * CAPTCHA Challenge<br>
     * 
     * @author Naoto Murata
     * @since 1
     * @version 5
     * @see "http://code.google.com/intl/ja/apis/accounts/docs/AuthForInstalledApps.html"
     */
    final public static class CaptchaChallenge extends Status {

        /** serialVersionUID */
        private static final long serialVersionUID = -4521278850353431128L;

        /** ClientLogin URL */
        private static final String CAPTCHA_IMAGE_URL_PREFIX = "http://www.google.com/accounts/";

        /** Application tag */
        private String mTag;

        /** "GOOGLE", "HOSTED", or "HOSTED_OR_GOOGLE" */
        private String mAccountType;

        /** Email address */
        private String mEmail;

        /** Password for user */
        private String mPassword;

        /** Name of the Google service you're requesting authorization for */
        private String mService;

        /** Short string identifying your application */
        private String mSource;

        /** Authentication parameter from Google */
        private String mCaptchaToken;

        /** Authentication image URL from Google */
        private String mCaptchaUrl;

        /** Authenticated */
        private boolean mAuthenticated = false;

        /**
         * Constructor
         * 
         * @param tag
         *            Application tag
         * @param accountType
         *            "GOOGLE", "HOSTED", or "HOSTED_OR_GOOGLE"
         * @param email
         *            Email address
         * @param password
         *            Password for user
         * @param service
         *            Name of the Google service you're requesting authorization
         *            for
         * @param source
         *            Short string identifying your application
         * @param captchaToken
         *            Authentication parameter from Google
         * @param captchaUrl
         *            Authentication image URL from Google
         */
        public CaptchaChallenge(String tag, String accountType, String email,
                String password, String service, String source,
                String captchaToken, String captchaUrl) {

            super(SC_FORBIDDEN, null, false);

            mTag = tag;
            mAccountType = accountType;
            mEmail = email;
            mPassword = password;
            mService = service;
            mSource = source;
            mCaptchaToken = captchaToken;
            mCaptchaUrl = captchaUrl;
        }

        /**
         * Post https authentication request again using captcha.
         * 
         * @param logincaptcha
         *            Authenticating CAPTCHA code.
         * @return Connection
         * @throws IOException
         *             Throws when network error has occurred.
         */
        public Status auth(String logincaptcha) throws IOException {
            try {
                // *** Request operation.
                HttpResponse res = postAuth(mAccountType, mEmail, mPassword,
                        mService, mSource, mCaptchaToken, logincaptcha);

                // *** Response operation.
                // 200 : Authenticated.
                // else : Error.
                int sc = res.getStatusLine().getStatusCode();
                switch (sc) {
                case SC_OK:

                    Map<String, String> m = parseResponseBody(res.getEntity());
                    mAuthenticated = true;
                    return new Status(SC_OK, m.get("Auth"), true);

                default:
                    return new Status(sc, null, false);
                }
            } catch (IOException e) {
                Log.w(mTag, "Network error.", e);
                throw e;
            }
        }

        /**
         * Gets Authentication image URL from Google.
         * 
         * @return Authentication parameter from Google
         */
        public String getCaptchaUrl() {
            return CAPTCHA_IMAGE_URL_PREFIX + mCaptchaUrl;
        }

        /**
         * Gets whether Google ClientLogin is authenticated or not.
         * 
         * @return Always false (overrides in subclasses)
         */
        @Override
        public boolean isAuthenticated() {
            return mAuthenticated;
        }

    }

    /**
     * <strong>Android implementation</strong> for<br>
     * Authentication and Authorization for Google APIs<br>
     * ClientLogin for Installed Applications<br>
     * <br>
     * Final authentication status<br>
     * 
     * @author Naoto Murata
     * @since 1
     * @version 5
     * @see "http://code.google.com/intl/ja/apis/accounts/docs/AuthForInstalledApps.html"
     */
    public static class Status implements Serializable {

        /** serialVersionUID */
        private static final long serialVersionUID = 6945252882607460292L;

        /** HTTP status code */
        int mCode;

        /**
         * Google ClientLogin 'Auth' value<br>
         * 
         * @see "http://code.google.com/intl/ja/apis/accounts/docs/AuthForInstalledApps.html"
         */
        String mAuth;

        /** Whether Google ClientLogin is authenticated or not. */
        boolean mAuthenticated;

        /**
         * Constructor
         * 
         * @param code
         *            HTTP status code
         * @param auth
         *            Google ClientLogin 'Auth' value
         * @param authenticated
         *            Whether Google ClientLogin is authenticated or not
         */
        public Status(int code, String auth, boolean authenticated) {
            mCode = code;
            mAuth = auth;
            mAuthenticated = authenticated;
        }

        /**
         * Gets HTTP status code.
         * 
         * @return HTTP status code
         */
        public String getAuth() {
            return mAuth;
        }

        /**
         * Gets Google ClientLogin 'Auth' value.
         * 
         * @return Google ClientLogin 'Auth' value
         */
        public int getCode() {
            return mCode;
        }

        /**
         * Gets whether Google ClientLogin is authenticated or not.
         * 
         * @return Always false
         */
        public boolean isAuthenticated() {
            return mAuthenticated;
        }
    }

    /** ClientLogin URL */
    private static final String CLIENT_LOGIN_URL = "https://www.google.com/accounts/ClientLogin";

    /** mAccountType parameter */
    public static final String ACCT_TYPE_GOOGLE = "GOOGLE";

    /** mAccountType parameter */
    public static final String ACCT_TYPE_HOSTED = "HOSTED";

    /** mAccountType parameter */
    public static final String ACCT_TYPE_HOSTED_OR_GOOGLE = "HOSTED_OR_GOOGLE";

    /**
     * Post https authentication request.
     * 
     * @param tag
     *            Application tag
     * @param accountType
     *            "GOOGLE", "HOSTED", or "HOSTED_OR_GOOGLE"
     * @param email
     *            Email address
     * @param password
     *            Password for user
     * @param service
     *            Name of the Google service you're requesting authorization for
     * @param source
     *            Short string identifying your application
     * @return Authentication status<br>
     * @throws IOException
     *             Throws when network error has occurred.
     */
    public static Status auth(String tag, String accountType, String email,
            String password, String service, String source) throws IOException {

        try {
            // *** Request operation.
            HttpResponse res = postAuth(accountType, email, password, service,
                    source);

            // *** Response operation.
            // 200 : Authenticated.
            // 403 w/ Error=CaptchaRequired : Return CaptchaChallenge.
            // 403 w/o Error=CaptchaRequired : Unauthorized.
            // else : Error.
            switch (res.getStatusLine().getStatusCode()) {
            case SC_OK: {
                Map<String, String> m = parseResponseBody(res.getEntity());
                return new Status(SC_OK, m.get("Auth"), true);
            }

            case SC_FORBIDDEN: {

                // Parse response body.
                Map<String, String> m = parseResponseBody(res.getEntity());

                // CaptchaChallenge or Unauthorized.
                if ("CaptchaRequired".equalsIgnoreCase(m.get("Error"))) {
                    return new CaptchaChallenge(tag, accountType, email,
                            password, service, source, m.get("CaptchaToken"), m
                                    .get("CaptchaUrl"));
                } else {
                    return new Status(SC_FORBIDDEN, null, false);
                }
            }

            default: {
                return new Status(res.getStatusLine().getStatusCode(), null,
                        false);
            }
            }
        } catch (IOException e) {
            Log.w(tag, "Network error.", e);
            throw e;
        }
    }

    /**
     * Parse response body of an authentication request.
     * 
     * @param body
     *            Http response body
     * @return Parsed response body
     * @throws IOException
     *             Throws when network error has occurred.
     */
    static Map<String, String> parseResponseBody(HttpEntity body)
            throws IOException {

        Map<String, String> m = new HashMap<String, String>();

        if (body != null) {
            String line = null;
            BufferedReader in = new BufferedReader(new InputStreamReader(body
                    .getContent()));
            while ((line = in.readLine()) != null) {
                if (!("".equals(line))) {
                    String[] kv = line.split("=", 2);
                    m.put(kv[0], kv[1]);
                }
            }
        }

        return m;
    }

    /**
     * Post https authentication request.
     * 
     * @param accountType
     *            "GOOGLE", "HOSTED", or "HOSTED_OR_GOOGLE"
     * @param email
     *            Email address
     * @param password
     *            Password for user
     * @param service
     *            Name of the Google service you're requesting authorization for
     * @param source
     *            Short string identifying your application
     * @return Connection
     * @throws IOException
     *             Throws when network error has occurred.
     */
    private static HttpResponse postAuth(String accountType, String email,
            String password, String service, String source) throws IOException {
        return postAuth(accountType, email, password, service, source, null,
                null);
    }

    /**
     * Post https authentication request.
     * 
     * @param accountType
     *            "GOOGLE", "HOSTED", or "HOSTED_OR_GOOGLE"
     * @param email
     *            Email address
     * @param password
     *            Password for user
     * @param service
     *            Name of the Google service you're requesting authorization for
     * @param source
     *            Short string identifying your application
     * @param logintoken
     *            Authenticating parameter from Google.
     * @param logincaptcha
     *            Authenticating CAPTCHA code.
     * @return Connection
     * @throws IOException
     *             Throws when network error has occurred.
     */
    static HttpResponse postAuth(String accountType, String email,
            String password, String service, String source, String logintoken,
            String logincaptcha) throws IOException {

        HttpClient client = new DefaultHttpClient();
        HttpPost post = new HttpPost(CLIENT_LOGIN_URL);

        List<NameValuePair> params = new ArrayList<NameValuePair>();
        params.add(new BasicNameValuePair("accountType", accountType));
        params.add(new BasicNameValuePair("Email", email));
        params.add(new BasicNameValuePair("Passwd", password));
        params.add(new BasicNameValuePair("service", service));
        params.add(new BasicNameValuePair("source", source));
        if (logintoken != null && logincaptcha != null) {
            params.add(new BasicNameValuePair("logintoken", logintoken));
            params.add(new BasicNameValuePair("logincaptcha", logincaptcha));
        }
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(params);

        post.addHeader("Content-Type", "application/x-www-form-urlencoded");
        post.setEntity(entity);

        return client.execute(post);
    }

    /** Private constructor */
    private NClientLogin() {
        //
    }

}
